<?php
declare(strict_types=1);

namespace So\Library;

use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
use So\Request;
use So\Cache;
use function PHPUnit\Framework\assertDirectoryDoesNotExist;

/**
 * 通用Auth权限类 TP框架思路实现 不光只用于admin
 * @author Saopig <1306222220@qq.com>
 */
class Auth
{

    /**
     * @Inject()
     * @var Request
     */
    protected $request;

    /**
     * @Inject()
     * @var Cache
     */
    protected $cache;

    // 错误信息
    protected string $_error = '';

    // 默认配置
    protected array $config = [
        // 用户组数据表名
        'auth_group' => 'auth_group',
        // 用户-用户组关系表
        'auth_group_access' => 'auth_group_access',
        // 权限规则表
        'auth_rule' => 'auth_rule',
        // 用户信息表
        'auth_user' => 'admin',
    ];

    /**
     * @param string $name
     * @return mixed
     */
    public function __get(string $name)
    {
        $this->jwt($name);
        return $this->{$name};
    }

    /**
     * 登录
     * @param string $username
     * @param string $password
     * @return bool
     */
    public function login(string $username, string $password): bool|array
    {
        $admin = Db::table($this->config['auth_user'])->where(['username' => $username])->first();
        if (!$admin) {
            $this->setError('账户或密码错误');
            return false;
        }
        if (!$admin['status']) {
            $this->setError('账户已被禁用');
            return false;
        }
        if ($admin['loginfailure'] >= 10 && time() - $admin['updatetime'] < 86400) {
            $this->setError('密码验证次数已达上线，请明日再试');
            return false;
        }
        if ($admin['password'] != md5(md5($password) . $admin['salt'])) {
            Db::table($this->config['auth_user'])->where(['id' => $admin['id']])->increment('loginfailure', 1);
            $this->setError('账户或密码错误');
            return false;
        }
        $rowtime = time();
        $token = Jwt::encode(array_merge([
            'auth' => $this->config['auth_user'],
            'time' => $rowtime,
        ], $admin));
        Db::table($this->config['auth_user'])->where(['id' => $admin['id']])->update([
            'loginfailure' => 0,
            'logintime' => $rowtime,
            'loginip' => $this->request->ip(),
        ]);
        return [
            'id' => $admin['id'],
            'username' => $admin['username'],
            'avatar' => $admin['avatar'],
            'email' => $admin['email'],
            'token' => $token
        ];
    }

    /**
     * 退出登录
     */
    public function logout(): bool
    {
        $token = $this->jwt('token');
        if ($token === false) return false;
        $adminId = $this->jwt('id');
        foreach ($this->config as $k => $v) {
            $this->cache->delete($k . $adminId);
        }
        return Jwt::black($token);
    }

    /**
     * 鉴权
     * @param mixed|null $uri
     * @return bool
     */
    public function check(mixed $uri = null, array $nocheck = [], array $nologin = []): bool
    {
        if (is_null($uri)) $uri = $this->request->getPath();
        if (in_array($uri, $nologin)) {
            return true;
        }
        $adminId = $this->jwt();
        if ($adminId === false) return false;
        if (!in_array($uri, $nocheck)) {
            $path = $this->checkPath($uri, $adminId);
            if (!$path) {
                $this->setError('没有权限访问');
                return false;
            }
        }
        return true;
    }

    /**
     * 解析Token获取管理员
     * @param string $key
     * @return mixed
     */
    public function jwt(string $key = 'id'): mixed
    {
        $token = $this->request->getHeaderLine('Authorization');
        $token = str_replace('Bearer ', '', $token);
        if (!$token) {
            $this->setError('未传入Token');
            return false;
        }
        try {
            $data = (array)Jwt::decode($token);
        } catch (\Exception $exception) {
            $this->setError('Token失效');
            return false;
        }
        $data['token'] = $token;
        $this->{$key} = $data[$key];
        return $data[$key];
    }

    /**
     * 检查权限
     * @param string|array $uri 需要验证的规则列表,支持逗号分隔的权限规则或索引数组
     * @param int $adminId 认证用户的id
     * @param string $relation 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
     * @return bool 通过验证返回true;失败返回false
     */
    public function checkPath(mixed $uri, int $adminId, string $relation = 'or'): bool
    {
        $rulelist = $this->getRuleList($adminId, true);
        if (in_array('*', $rulelist)) {
            return true;
        }
        // 检测传入权限是否多个
        if (is_string($uri)) {
            $uri = strtolower($uri);
            if (strpos($uri, ',') !== false) {
                $uri = explode(',', $uri);
            } else {
                $uri = [$uri];
            }
        }
        $list = []; //保存验证通过的规则名
        $REQUEST = unserialize(strtolower(serialize($this->request->all())));
        foreach ($rulelist as $rule) {
            $query = preg_replace('/^.+\?/U', '', $rule);
            if ($query != $rule) {
                parse_str($query, $param); //解析规则中的param
                $intersect = array_intersect_assoc($REQUEST, $param);
                $rule = preg_replace('/\?.*$/U', '', $rule);
                if (in_array($rule, $uri) && $intersect == $param) {
                    //如果节点相符且url参数满足
                    $list[] = $rule;
                }
            } else {
                if (in_array($rule, $uri)) {
                    $list[] = $rule;
                }
            }
        }
        if ('or' == $relation && !empty($list)) {
            return true;
        }
        $diff = array_diff($uri, $list);
        if ('and' == $relation && empty($diff)) {
            return true;
        }
        return false;
    }

    /**
     * 获取所有规则
     * @param int $adminId
     * @param bool $check
     * @return array|string[]
     */
    public function getRuleList(int $adminId, bool $check = false, bool $filter = true): array
    {
        $ids = $this->getRuleIds($adminId);
        if (empty($ids)) {
            return [];
        }
        if (in_array('*', $ids) && $check) {
            return ['*'];
        }
        //读取用户组所有权限规则
        if (!in_array('*', $ids)) {
            $rules = $this->cache->get($this->config['auth_rule'] . $adminId) ?: Db::table($this->config['auth_rule'])->whereIn('id', $ids)->get()->toArray();
        } else {
            $rules = $this->cache->get($this->config['auth_rule'] . $adminId) ?: Db::table($this->config['auth_rule'])->get()->toArray();
        }
        $this->cache->set($this->config['auth_rule'] . $adminId, $rules, 600);
        $rulelist = [];
        foreach ($rules as $rule) {
            $rulelist[$rule['id']] = $filter ? strtolower(str_replace('_', '/', $rule['name'])) : $rule;
        }
        return $rulelist;
    }

    /**
     * 是否为超级管理员
     * @param int $adminId
     * @return bool
     */
    public function isSuperAdmin(int $adminId): bool
    {
        return in_array('*', $this->getRuleIds($adminId)) ? true : false;
    }

    /**
     * 获取用户组关系
     * @param int $adminId
     * @return array
     */
    public function getGroups(int $adminId): array
    {
        $auth_group_access = $this->config['auth_group_access'];
        $auth_group = $this->config['auth_group'];
        $user_groups = $this->cache->get($this->config['auth_group'] . $adminId) ?: Db::table($auth_group_access)->leftJoin($auth_group, $auth_group . '.id', $auth_group_access . '.group_id')->where([[$auth_group_access . ".uid", '=', $adminId], [$auth_group . ".status", '=', 1],])->select($auth_group_access . '.uid', $auth_group_access . '.group_id', $auth_group . '.id', $auth_group . '.pid', $auth_group . '.name', $auth_group . '.rules')->get()->toArray();
        $this->cache->set($this->config['auth_group'] . $adminId, $user_groups, 600);
        return $user_groups;
    }

    /**
     * 用户组关系ID转换
     * @param int $adminId
     * @return array
     */
    public function getRuleIds(int $adminId): array
    {
        $user_groups = $this->getGroups($adminId);
        $ids = [];
        foreach ($user_groups as $g) {
            $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
        }
        return array_unique($ids);
    }

    /**
     * 取出当前管理员所拥有权限的分组
     * @param boolean $withself 是否包含当前所在的分组
     * @return array
     */
    public function getChildrenGroupIds($withself = false)
    {
        //取出当前管理员所有的分组
        $groups = $this->getGroups($this->id);
        $groupIds = [];
        foreach ($groups as $k => $v) {
            $groupIds[] = $v['id'];
        }
        $originGroupIds = $groupIds;
        foreach ($groups as $k => $v) {
            if (in_array($v['pid'], $originGroupIds)) {
                $groupIds = array_diff($groupIds, [$v['id']]);
                unset($groups[$k]);
            }
        }

        // 取出所有分组
        $groupList = Db::table($this->config['auth_group'])->where('status', '=', 1)->get();
        $objList = [];
        foreach ($groups as $k => $v) {
            if ($v['rules'] === '*') {
                $objList = $groupList;
                break;
            }
            // 取出包含自己的所有子节点
            $childrenList = Tree::instance()->init($groupList, 'pid')->getChildren($v['id'], true);
            $obj = Tree::instance()->init($childrenList, 'pid')->getTreeArray($v['pid']);
            $objList = array_merge($objList, Tree::instance()->getTreeList($obj));
        }
        $childrenGroupIds = [];
        foreach ($objList as $k => $v) {
            $childrenGroupIds[] = $v['id'];
        }

        if (!$withself) {
            $childrenGroupIds = array_diff($childrenGroupIds, $groupIds);
        }
        return $childrenGroupIds;
    }

    /**
     * 取出当前管理员所拥有权限的管理员
     * @param boolean $withself 是否包含自身
     * @return array
     */
    public function getChildrenAdminIds($withself = false)
    {
        $childrenAdminIds = [];
        if (!$this->isSuperAdmin($this->id)) {
            $groupIds = $this->getChildrenGroupIds(false);
            $authGroupList = Db::table($this->config['auth_group_access'])->
            select('uid', 'group_id')
                ->where('group_id', 'in', $groupIds)
                ->get();
            foreach ($authGroupList as $k => $v) {
                $childrenAdminIds[] = $v['uid'];
            }
        } else {
            //超级管理员拥有所有人的权限
            $childrenAdminIds = Db::table($this->config['auth_user'])->pluck('id')->toArray();
        }
        if ($withself) {
            if (!in_array($this->id, $childrenAdminIds)) {
                $childrenAdminIds[] = $this->id;
            }
        } else {
            $childrenAdminIds = array_diff($childrenAdminIds, [$this->id]);
        }
        return $childrenAdminIds;
    }

    /**
     * 设置错误信息
     *
     * @param string $error 错误信息
     * @return Auth
     */
    public function setError($error)
    {
        $this->_error = $error;
        return $this;
    }

    /**
     * 获取错误信息
     * @return string
     */
    public function getError()
    {
        return $this->_error ? $this->_error : '';
    }

    /**
     * 设置配置文件
     * @param array $config
     * @return $this
     */
    public function setConfig(array $config)
    {
        $this->config = array_merge($this->config, $config);
        return $this;
    }
}